Skip to main content
  1. Writing/

Caffeinate Windows With PowerShell

·599 words

Fall is officially in full swing on the West Coast - seriously, look at this weather.

Fall leaves falling on a street

It’s the perfect time to drink coffee, sit inside, and code. There was just one problem with that for me - I actually need to step away from my machine from time to time to brew some coffee, and while I was gone, the computer would go to sleep, and I needed to wake it up, enter my credentials. Wasting precious 3 seconds, every hour or so. Because I am at home, I am not worried about some malicious actor snooping over my desktop and the open browser tabs (most open to “how to write output powershell” or some variation of queries that kick the impostor syndrome in full gear).

On macOS, there is a very helpful command for this very purpose - caffeinate -d. Windows has nothing like that out of the box, but that doesn’t mean I can’t hack something together that gets the job done with PowerShell. There are some scripts in the wild that try to press the Scroll Lock button virtually, but I don’t consider that to be an elegant solution. There has to be a better way.

Lucky for us, there is an API that Windows has, called SetThreadExecutionState1, that can be used for the purpose of this project. This is the same API that is used by media players, for example, when they need to keep the screens on whenever a movie is playing on your computer. We can re-purpose it to be our Windows caffeination tool.

Let’s get to the script first - you can use it right away:

function Drink-Espresso {
    Write-Host "[info] Currently ordering a double shot of espresso..."

    $Signature=@"
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SetThreadExecutionState(uint esFlags);
"@

    $ES_DISPLAY_REQUIRED = [uint32]"0x00000002"
    $ES_CONTINUOUS = [uint32]"0x80000000"

    $JobName = "DrinkALotOfEspresso"

    try
    {
        $BackgroundJob = Start-Job -Name $JobName -ScriptBlock {
            $STES = Add-Type -MemberDefinition $args[0] -Name System -Namespace Win32 -PassThru

            $STES::SetThreadExecutionState($args[2] -bor $args[1])

            while ($true)
            {
                Start-Sleep -s 15
            }
        } -ArgumentList $Signature, $ES_DISPLAY_REQUIRED, $ES_CONTINUOUS

        Wait-Job $BackgroundJob
    }
    finally
    {
        Stop-Job -Name $JobName
        Remove-Job -Name $JobName
        Write-Host "[info] No more espressos left behind the counter."
    }
}

First, if you are at least somewhat familiar with P/Invoke, I am re-creating the managed signature for the native Windows API. Then, following the guidelines in the documentation for SetThreadExecutionState, I am defining the unsigned integer values for the two flags that we need to pass to the API. There is also a job name defined, and you might be wondering why I need it.

Well, as it turns out, calling Add-Type2 in the same PowerShell session can lead you to an error, that stems from how .NET CLR handles types within the current application context. So, what I end up doing is creating background jobs, that run within their own, dedicated context, that allows me to use the Add-Type cmdlet without any blockers. To start the job, I rely on standard PowerShell tools to handle background execution3. Inside the background job, I am calling the API as if I am an application that needs to have the display on.

When I terminate the script with Ctrl+C, the job is stopped and removed - that’s it! Now I can make my computer be awake just like I could on macOS. To run the script, you can save it locally, and run:

. .\your-script.ps1
Drink-Espresso

  1. You can read more about it in the official documentation↩︎

  2. Once again, refer to docs.microsoft.com for details. ↩︎

  3. You can read more in the official docs as well: Start-Job and Wait-Job↩︎